#define DEBUG
using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace ICSharpCode.TextEditor.Document
{
	internal sealed class LineManager
	{
		private sealed class DelimiterSegment
		{
			internal int Offset;

			internal int Length;
		}

		private LineSegmentTree lineCollection = new LineSegmentTree();

		private IDocument document;

		private IHighlightingStrategy highlightingStrategy;

		private DelimiterSegment delimiterSegment = new DelimiterSegment();

		public IList<LineSegment> LineSegmentCollection => this.lineCollection;

		public int TotalNumberOfLines => this.lineCollection.Count;

		public IHighlightingStrategy HighlightingStrategy
		{
			get
			{
				return this.highlightingStrategy;
			}
			set
			{
				if (this.highlightingStrategy != value)
				{
					this.highlightingStrategy = value;
					if (this.highlightingStrategy != null)
					{
						this.highlightingStrategy.MarkTokens(this.document);
					}
				}
			}
		}

		public event EventHandler<LineLengthChangeEventArgs> LineLengthChanged;

		public event EventHandler<LineCountChangeEventArgs> LineCountChanged;

		public event EventHandler<LineEventArgs> LineDeleted;

		public LineManager(IDocument document, IHighlightingStrategy highlightingStrategy)
		{
			this.document = document;
			this.highlightingStrategy = highlightingStrategy;
		}

		public int GetLineNumberForOffset(int offset)
		{
			return this.GetLineSegmentForOffset(offset).LineNumber;
		}

		public LineSegment GetLineSegmentForOffset(int offset)
		{
			return this.lineCollection.GetByOffset(offset);
		}

		public LineSegment GetLineSegment(int lineNr)
		{
			return this.lineCollection[lineNr];
		}

		public void Insert(int offset, string text)
		{
			this.Replace(offset, 0, text);
		}

		public void Remove(int offset, int length)
		{
			this.Replace(offset, length, string.Empty);
		}

		public void Replace(int offset, int length, string text)
		{
			Debug.WriteLine("Replace offset=" + offset + " length=" + length + " text.Length=" + text.Length);
			int lineNumberForOffset = this.GetLineNumberForOffset(offset);
			int totalNumberOfLines = this.TotalNumberOfLines;
			DeferredEventList deferredEventList = default(DeferredEventList);
			this.RemoveInternal(ref deferredEventList, offset, length);
			int totalNumberOfLines2 = this.TotalNumberOfLines;
			if (!string.IsNullOrEmpty(text))
			{
				this.InsertInternal(offset, text);
			}
			this.RunHighlighter(lineNumberForOffset, 1 + Math.Max(0, this.TotalNumberOfLines - totalNumberOfLines2));
			if (deferredEventList.removedLines != null)
			{
				foreach (LineSegment removedLine in deferredEventList.removedLines)
				{
					this.OnLineDeleted(new LineEventArgs(this.document, removedLine));
				}
			}
			deferredEventList.RaiseEvents();
			if (this.TotalNumberOfLines != totalNumberOfLines)
			{
				this.OnLineCountChanged(new LineCountChangeEventArgs(this.document, lineNumberForOffset, this.TotalNumberOfLines - totalNumberOfLines));
			}
		}

		private void RemoveInternal(ref DeferredEventList deferredEventList, int offset, int length)
		{
			Debug.Assert(length >= 0);
			if (length == 0)
			{
				return;
			}
			LineSegmentTree.Enumerator enumeratorForOffset = this.lineCollection.GetEnumeratorForOffset(offset);
			LineSegment current = enumeratorForOffset.Current;
			int offset2 = current.Offset;
			if (offset + length < offset2 + current.TotalLength)
			{
				current.RemovedLinePart(ref deferredEventList, offset - offset2, length);
				this.SetSegmentLength(current, current.TotalLength - length);
				return;
			}
			int num = offset2 + current.TotalLength - offset;
			Debug.Assert(num > 0);
			current.RemovedLinePart(ref deferredEventList, offset - offset2, num);
			LineSegment byOffset = this.lineCollection.GetByOffset(offset + length);
			if (byOffset == current)
			{
				this.SetSegmentLength(current, current.TotalLength - length);
				return;
			}
			int offset3 = byOffset.Offset;
			int num2 = offset3 + byOffset.TotalLength - (offset + length);
			byOffset.RemovedLinePart(ref deferredEventList, 0, byOffset.TotalLength - num2);
			current.MergedWith(byOffset, offset - offset2);
			this.SetSegmentLength(current, current.TotalLength - num + num2);
			current.DelimiterLength = byOffset.DelimiterLength;
			enumeratorForOffset.MoveNext();
			LineSegment current2;
			do
			{
				current2 = enumeratorForOffset.Current;
				enumeratorForOffset.MoveNext();
				this.lineCollection.RemoveSegment(current2);
				current2.Deleted(ref deferredEventList);
			}
			while (current2 != byOffset);
		}

		private void InsertInternal(int offset, string text)
		{
			LineSegment lineSegment = this.lineCollection.GetByOffset(offset);
			DelimiterSegment delimiterSegment = this.NextDelimiter(text, 0);
			if (delimiterSegment == null)
			{
				lineSegment.InsertedLinePart(offset - lineSegment.Offset, text.Length);
				this.SetSegmentLength(lineSegment, lineSegment.TotalLength + text.Length);
				return;
			}
			LineSegment lineSegment2 = lineSegment;
			lineSegment2.InsertedLinePart(offset - lineSegment2.Offset, delimiterSegment.Offset);
			int num = 0;
			while (delimiterSegment != null)
			{
				int num2 = offset + delimiterSegment.Offset + delimiterSegment.Length;
				int offset2 = lineSegment.Offset;
				int length = offset2 + lineSegment.TotalLength - (offset + num);
				this.lineCollection.SetSegmentLength(lineSegment, num2 - offset2);
				LineSegment lineSegment3 = this.lineCollection.InsertSegmentAfter(lineSegment, length);
				lineSegment.DelimiterLength = delimiterSegment.Length;
				lineSegment = lineSegment3;
				num = delimiterSegment.Offset + delimiterSegment.Length;
				delimiterSegment = this.NextDelimiter(text, num);
			}
			lineSegment2.SplitTo(lineSegment);
			if (num != text.Length)
			{
				lineSegment.InsertedLinePart(0, text.Length - num);
				this.SetSegmentLength(lineSegment, lineSegment.TotalLength + text.Length - num);
			}
		}

		private void SetSegmentLength(LineSegment segment, int newTotalLength)
		{
			int num = newTotalLength - segment.TotalLength;
			if (num != 0)
			{
				this.lineCollection.SetSegmentLength(segment, newTotalLength);
				this.OnLineLengthChanged(new LineLengthChangeEventArgs(this.document, segment, num));
			}
		}

		private void RunHighlighter(int firstLine, int lineCount)
		{
			if (this.highlightingStrategy == null)
			{
				return;
			}
			List<LineSegment> list = new List<LineSegment>();
			LineSegmentTree.Enumerator enumeratorForIndex = this.lineCollection.GetEnumeratorForIndex(firstLine);
			for (int i = 0; i < lineCount; i++)
			{
				if (!enumeratorForIndex.IsValid)
				{
					break;
				}
				list.Add(enumeratorForIndex.Current);
				enumeratorForIndex.MoveNext();
			}
			this.highlightingStrategy.MarkTokens(this.document, list);
		}

		public void SetContent(string text)
		{
			this.lineCollection.Clear();
			if (text != null)
			{
				this.Replace(0, 0, text);
			}
		}

		public int GetVisibleLine(int logicalLineNumber)
		{
			if (!this.document.TextEditorProperties.EnableFolding)
			{
				return logicalLineNumber;
			}
			int num = 0;
			int num2 = 0;
			List<FoldMarker> topLevelFoldedFoldings = this.document.FoldingManager.GetTopLevelFoldedFoldings();
			foreach (FoldMarker item in topLevelFoldedFoldings)
			{
				if (item.StartLine >= logicalLineNumber)
				{
					break;
				}
				if (item.StartLine >= num2)
				{
					num += item.StartLine - num2;
					if (item.EndLine > logicalLineNumber)
					{
						return num;
					}
					num2 = item.EndLine;
				}
			}
			return num + (logicalLineNumber - num2);
		}

		public int GetFirstLogicalLine(int visibleLineNumber)
		{
			if (!this.document.TextEditorProperties.EnableFolding)
			{
				return visibleLineNumber;
			}
			int num = 0;
			int num2 = 0;
			List<FoldMarker> topLevelFoldedFoldings = this.document.FoldingManager.GetTopLevelFoldedFoldings();
			foreach (FoldMarker item in topLevelFoldedFoldings)
			{
				if (item.StartLine >= num2)
				{
					if (num + item.StartLine - num2 >= visibleLineNumber)
					{
						break;
					}
					num += item.StartLine - num2;
					num2 = item.EndLine;
				}
			}
			topLevelFoldedFoldings.Clear();
			topLevelFoldedFoldings = null;
			return num2 + visibleLineNumber - num;
		}

		public int GetLastLogicalLine(int visibleLineNumber)
		{
			if (!this.document.TextEditorProperties.EnableFolding)
			{
				return visibleLineNumber;
			}
			return this.GetFirstLogicalLine(visibleLineNumber + 1) - 1;
		}

		public int GetNextVisibleLineAbove(int lineNumber, int lineCount)
		{
			int i = lineNumber;
			if (this.document.TextEditorProperties.EnableFolding)
			{
				for (int j = 0; j < lineCount; j++)
				{
					if (i >= this.TotalNumberOfLines)
					{
						break;
					}
					for (i++; i < this.TotalNumberOfLines && (i >= this.lineCollection.Count || !this.document.FoldingManager.IsLineVisible(i)); i++)
					{
					}
				}
			}
			else
			{
				i += lineCount;
			}
			return Math.Min(this.TotalNumberOfLines - 1, i);
		}

		public int GetNextVisibleLineBelow(int lineNumber, int lineCount)
		{
			int num = lineNumber;
			if (this.document.TextEditorProperties.EnableFolding)
			{
				for (int i = 0; i < lineCount; i++)
				{
					num--;
					while (num >= 0 && !this.document.FoldingManager.IsLineVisible(num))
					{
						num--;
					}
				}
			}
			else
			{
				num -= lineCount;
			}
			return Math.Max(0, num);
		}

		private DelimiterSegment NextDelimiter(string text, int offset)
		{
			int num = offset;
			while (num < text.Length)
			{
				char c = text[num];
				char c2 = c;
				if (c2 != '\n')
				{
					if (c2 != '\r')
					{
						num++;
						continue;
					}
					if (num + 1 < text.Length && text[num + 1] == '\n')
					{
						this.delimiterSegment.Offset = num;
						this.delimiterSegment.Length = 2;
						return this.delimiterSegment;
					}
				}
				this.delimiterSegment.Offset = num;
				this.delimiterSegment.Length = 1;
				return this.delimiterSegment;
			}
			return null;
		}

		private void OnLineCountChanged(LineCountChangeEventArgs e)
		{
			if (this.LineCountChanged != null)
			{
				this.LineCountChanged(this, e);
			}
		}

		private void OnLineLengthChanged(LineLengthChangeEventArgs e)
		{
			if (this.LineLengthChanged != null)
			{
				this.LineLengthChanged(this, e);
			}
		}

		private void OnLineDeleted(LineEventArgs e)
		{
			if (this.LineDeleted != null)
			{
				this.LineDeleted(this, e);
			}
		}
	}
}
